using System;
using System.Windows.Forms;
using CSW.Framework.Common;
using CSW.Framework.Common.Binding.Interfaces;
using CSW.Framework.Common.Binding.ValueTypes;

namespace CSW.Framework.UI.Binding
{
    /// <summary>
    /// Provides capability to bind controls and error providers to properties in an <see cref="IBusinessObject" />.
    /// </summary>
    public class EditFormBinder<T> : Binder<T>
        where T : IBusinessObject
    {
        private readonly T m_BusinessObject;
        private readonly Form m_EditForm;
        private readonly string m_EditFormBaseCaption;
        private readonly bool m_IsParentRecord;
        private bool m_IgnoreCancelWarning;

        /// <summary>
        /// Occurs just before the record is saved.
        /// </summary>
        public event EventHandler BeforeSave;

        /// <summary>
        /// Initializes a new instance of the <see cref="EditFormBinder&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="form">The form.</param>
        /// <param name="businessObject">The business object to bind to.</param>
        /// <param name="errorProvider">The error provider.</param>
        /// <param name="saveButton">The save button.</param>
        /// <param name="caption">The base caption of the form. Can be null.</param>
        /// <param name="recordType">If set to <see cref="RecordType.Parent"/>, <see cref="IBusinessObject.TrySave()"/>
        /// will be called on the record when the save button is clicked. 
        /// Otherwise, <see cref="IBindableObject.TrySaveBaselineState()"/> will be called, which will
        /// save the object's current state but not save it to the database.
        /// The latter case is intended for child record editting.</param>
        /// <param name="showErrors">if set to <see cref="ShowErrors.AfterSave"/>, show errors only after save attempted (default = <see cref="ShowErrors.Immediately"/>).</param>
        public EditFormBinder(Form form, T businessObject, IErrorWrapper errorProvider,
                              Control saveButton, string caption, RecordType recordType, ShowErrors showErrors)
            : base(businessObject, errorProvider, showErrors)
        {
            Guard.ArgumentNotNull(form, "form");
            Guard.ArgumentNotNull(businessObject, "businessObject");
            Guard.ArgumentNotNull(errorProvider, "errorProvider");
            
            m_BusinessObject = businessObject;
            if (recordType == RecordType.Parent)
            {
                m_BusinessObject.Refresh();
            }

            m_EditForm = form;
            m_EditFormBaseCaption = caption;
            m_IsParentRecord = (recordType == RecordType.Parent);

            if (caption != null)
            {
                if (m_BusinessObject.IsNew)
                    form.Text = "New " + m_EditFormBaseCaption;
                else
                    form.Text = "Edit " + m_EditFormBaseCaption;

                m_BusinessObject.ObjectStateChanged += BusinessObject_ObjectStateChanged;
            }

            form.FormClosed += EditForm_FormClosed;
            form.FormClosing += EditForm_FormClosing;

            if (saveButton != null)
                saveButton.Click += SaveButton_Click;
        }

        /// <summary>
        /// Updates all controls. Calls <see cref="IBusinessObject.BeginEdit()"/> if the object is not currently in edit mode.
        /// </summary>
        public override void UpdateControls()
        {
            if (!m_BusinessObject.IsEditting)
                m_BusinessObject.BeginEdit(false);

            base.UpdateControls();
        }

        /// <summary>
        /// Tries to save the bound item.
        /// </summary>
        /// <param name="verbose">if set to <c>true</c> a message box will be displayed if there are business rule which fail.</param>
        /// <returns><c>true</c> if successful; otherwise, <c>false</c>.</returns>
        public bool TrySave(bool verbose)
        {
            ShowErrors = true;

            string errorMessage;
            bool success = TrySave(out errorMessage);
            if (verbose && !success)
                MessageBox.Show(errorMessage, "", MessageBoxButtons.OK, MessageBoxIcon.Information);
            return success;
        }

        /// <summary>
        /// Tries to save the bound item.
        /// </summary>
        /// <param name="errorMessage">The error message.</param>
        /// <returns><c>true</c> if successful; otherwise, <c>false</c>.</returns>
        public bool TrySave(out string errorMessage)
        {
            return TryPersist(out errorMessage, m_BusinessObject.TrySave);
        }

        private void BusinessObject_ObjectStateChanged(object sender, EventArgs e)
        {
            if (m_EditFormBaseCaption != null && !m_BusinessObject.IsNew)
                m_EditForm.Text = "Edit " + m_EditFormBaseCaption + (m_BusinessObject.ObjectState == BindableObjectState.Changed ? "*" : "");
        }

        private void SaveButton_Click(object sender, EventArgs e)
        {
            if (m_IsParentRecord)
            {
                if (Validate(true))
                {
                    EventHandler beforeSave = BeforeSave;
                    if (beforeSave != null)
                        beforeSave(this, EventArgs.Empty);

                    if (TrySave(true))
                    {
                        m_EditForm.DialogResult = DialogResult.OK;
                        m_EditForm.Close();
                    }
                }
            }
            else
            {
                if (TrySaveBaselineState(true))
                    CloseWithoutSave();
            }
        }

        private void EditForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (m_IgnoreCancelWarning)
                return;

            // cause validation events to fire
            ValidateAllControls();

            if (m_BusinessObject.IsEditting &&
                m_BusinessObject.ObjectState == BindableObjectState.Changed &&
                (e.CloseReason == CloseReason.UserClosing || e.CloseReason == CloseReason.None))
            {
                if (MessageBox.Show("Cancel changes?", "", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
                {
                    e.Cancel = true;
                }
                else
                {
                    // avoid freezing part of the cancel message over the form
                    Application.DoEvents();

                    Cursor.Current = Cursors.WaitCursor;
                    if (m_IsParentRecord)
                        m_BusinessObject.CancelEdit();
                    else
                        m_BusinessObject.RevertToBaselineState();
                    Cursor.Current = Cursors.Default;
                }
            }
        }

        private void EditForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (!m_IgnoreCancelWarning && m_IsParentRecord)
            {
                if (m_BusinessObject.IsEditting)
                    m_BusinessObject.CancelEdit();
            }

            Dispose();
        }

        /// <summary>
        /// Closes the form without saving.
        /// </summary>
        public void CloseWithoutSave()
        {
            Guard.ArgumentNotNull(m_EditForm, "m_EditForm");

            m_IgnoreCancelWarning = true;
            m_EditForm.DialogResult = DialogResult.OK;
            m_EditForm.Close();
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public override void Dispose()
        {
            m_BusinessObject.ObjectStateChanged -= BusinessObject_ObjectStateChanged;

            base.Dispose();
        }
    }
}
